/*-*-C-*-
 * Resizing gadgets for the Windows CSE
 */

#include "resed.h"
#include "main.h"

#include "swicall.h"
#include "wimp.h"
#include "resformat.h"
#include "newmsgs.h"
#include "dragdrop.h"
#include "interactor.h"

#include "format.h"
#include "relocate.h"
#include "windowedit.h"
#include "gadget.h"
#include "grid.h"
#include "protocol.h"
#include "resize.h"


typedef struct
{
    WindowObjPtr window;
    int earnum;            /* identifies resize ear being dragged */
    PointRec original;     /* original position of mouse (work) */
    PointRec offset;       /* current offset from original position */
    GadgetPtr gadget;      /* identifies numberrange gadget when earnum=4 */
} ResizeClosureRec, *ResizeClosurePtr;



/*
 * Take a gadget's bounding box, and calculate what the new bounding box
 *  would be if the given resize ear was moved by the given offset.
 *
 * [It used to be considered distracting for the dotted rectangle to snap to
 *  grid points as the user drags the mouse, so the boolean 'usegridlock' was
 *  provided - to be set only when determining the final size of the object
 *  at the end of the drag. This is currently always set TRUE.]
 */

static void calculate_new_bbox (
    WindowObjPtr window,
    GadgetPtr gadget,
    int earnum,
    PointPtr offset,
    RectPtr bbox,
    Bool usegridlock
)
{
    int xx = earnum % 3, yy = earnum / 3;
    PointRec p;

    if (xx == 0)
    {
        bbox->minx += offset->x;
        p.x = bbox->maxx - bbox->minx;
        if (p.x < gadget->def->minsize.x)
            p.x = gadget->def->minsize.x;
        if (usegridlock && window->grid.lock)
        {
            grid_snap_point(window, &p);
            if (p.x < gadget->def->minsize.x)
                p.x += window->grid.horizdiv;
        }
        bbox->minx = bbox->maxx - p.x;
    }
    else if (xx == 2)
    {
        bbox->maxx += offset->x;
        p.x = bbox->maxx - bbox->minx;
        if (p.x < gadget->def->minsize.x)
            p.x = gadget->def->minsize.x;
        if (usegridlock && window->grid.lock)
        {
            grid_snap_point(window, &p);
            if (p.x < gadget->def->minsize.x)
                p.x += window->grid.horizdiv;
        }
        bbox->maxx = bbox->minx + p.x;
    }

    if (yy == 0)
    {
        bbox->miny += offset->y;
        p.y = bbox->maxy - bbox->miny;
        if (p.y < gadget->def->minsize.y)
            p.y = gadget->def->minsize.y;
        if (usegridlock && window->grid.lock)
        {
            grid_snap_point(window, &p);
            if (p.y < gadget->def->minsize.y)
                p.y += window->grid.vertdiv;
        }
        bbox->miny = bbox->maxy - p.y;
    }
    else if (yy == 2)
    {
        bbox->maxy += offset->y;
        p.y = bbox->maxy - bbox->miny;
        if (p.y < gadget->def->minsize.y)
            p.y = gadget->def->minsize.y;
        if (usegridlock && window->grid.lock)
        {
            grid_snap_point(window, &p);
            if (p.y < gadget->def->minsize.y)
                p.y += window->grid.vertdiv;
        }
        bbox->maxy = bbox->miny + p.y;
    }
}


/*
 * Calculate new size of display field within numberrange gadget.
 * Sets 'hitboundary' TRUE iff pointer has gone "out-of-bounds".
 * Sets 'onrhs' TRUE iff numberrange has display field on rhs.
 */

static int calc_new_box_width (
    GadgetPtr gadget,
    int offset,
    Bool *hitboundary,
    Bool *onrhs
)
{
    int boxwidth = gadget->body.numberrange.displaylength;
    PointRec p;
    WindowObjPtr window = gadget->owner;

    offset = WIMP_ALIGN_COORD (offset);
    *hitboundary = FALSE;
    *onrhs = (gadget->hdr.flags & NUMBERRANGE_SLIDERTYPE) ==
                                          NUMBERRANGE_SLIDERLEFT;

    if (*onrhs)
        boxwidth -= offset;
    else
        boxwidth += offset;

    /* constrain display width to multiple of grid spacing if appropriate */
    if (window->grid.lock)
    {
        p.x = boxwidth;
        grid_snap_point (window, &p);
        boxwidth = p.x;
    }

    /* constrain size of display field */
    if (boxwidth < 16)
    {
        *hitboundary = TRUE;
        boxwidth = 16;
    }
    if (boxwidth > (gadget->hdr.bbox.maxx - gadget->hdr.bbox.minx) - 16)
    {
        *hitboundary = TRUE;
        boxwidth = (gadget->hdr.bbox.maxx - gadget->hdr.bbox.minx) - 16;
    }

    return boxwidth;
}


/*
 * Plot the dotted boxes for the selected gadgets using the offset and ear
 *  number supplied.
 * This is called from update_resize_boxes(..) below, and also from the
 *  main window redraw loop - windowedit_redraw_window_and_resize_boxes(..).
 */

error * resize_plot_boxes (
    WindowObjPtr window,
    void *closure
)
{
    RectRec bbox;
    ResizeClosurePtr resize = (ResizeClosurePtr) closure;
    int earnum = resize->earnum;
    PointPtr offset = &resize->offset;

    if (earnum == 4)
    {
        GadgetPtr gadget = resize->gadget;
        int boxwidth;
        Bool hitboundary, onrhs;

        boxwidth = calc_new_box_width (gadget, offset->x,
                                               &hitboundary, &onrhs);
        bbox = gadget->hdr.bbox;

        /* constrain mouse pointer if necessary */
        if (hitboundary)
        {
            RectRec bbox = gadget->hdr.bbox;
            char pb[9];  /* parameter block for OS_Word 21, 1 */

            if (bbox.maxx - bbox.minx > 40)
            {
                bbox.minx += 16;
                bbox.maxx -= 16;
            }

            wimp_convert_rect (WorkToScreen, window->window, &bbox, &bbox);

            /* set mouse coordinate bounding box */
            pb[0] = 1;
            pb[1] = bbox.minx;
            pb[2] = bbox.minx >> 8;
            pb[3] = window->window->visarea.miny;
            pb[4] = window->window->visarea.miny >> 8;
            pb[5] = bbox.maxx;
            pb[6] = bbox.maxx >> 8;
            pb[7] = window->window->visarea.maxy;
            pb[8] = window->window->visarea.maxy >> 8;
            swi (OS_Word, R0, 21, R1, pb, END);
        }

        if (onrhs)
            bbox.minx = bbox.maxx - boxwidth;
        else
            bbox.maxx = bbox.minx + boxwidth;

        wimp_plot_eor_box (window->window, &bbox);
    }
    else
    {
        GadgetPtr gadget = window->gadgets;

        while (gadget != NULL)
        {
            if (gadget->selected)
            {
                bbox = gadget->hdr.bbox;
                calculate_new_bbox (window, gadget, earnum, offset, &bbox,
                                    TRUE);
                wimp_plot_eor_box (window->window, &bbox);
            }
            gadget = gadget->next;
        }
    }

    return NULL;
}


/*
 * Plot dotted resize boxes for each selected gadget.
 * This may be called outside a redraw loop because it uses
 *  Wimp_UpdateWindow.
 */

static void update_resize_boxes (ResizeClosurePtr resize)
{
    WindowObjPtr window = resize->window;
    WindowRedrawRec temp;
    int more;
    
    temp.handle = window->window->handle;
    wimp_convert_rect (ScreenToWork, window->window,
                              &window->window->visarea, &temp.visarea);
    swi (Wimp_UpdateWindow,  R1, &temp,  OUT,  R0, &more,  END);
    
    while (more)
    {
        resize_plot_boxes (window, (void *) resize);
        swi (Wimp_GetRectangle,  R1,  &temp,  OUT,  R0, &more,  END);
    }
}


/*
 * Resize each selected gadget in 'window' by 'offset' - constrained by
 *  'earnum'.
 */

static error * resize_selected_gadgets (ResizeClosurePtr resize)
{
    int earnum = resize->earnum;
    WindowObjPtr window = resize->window;
    PointPtr offset = &resize->offset;

    if (earnum == 4)   /* resize display field inside numerrange */
    {
        GadgetPtr gadget = resize->gadget;
        Bool hitboundary, onrhs;
        int oldwidth = gadget->body.numberrange.displaylength;
        int newwidth =
            calc_new_box_width (gadget, offset->x, &hitboundary, &onrhs);

        /* nothing to do if width has not changed */
        if (oldwidth == newwidth)
            return NULL;

        /* set new display field length */
        gadget->body.numberrange.displaylength = newwidth;
            
        /* update corresponding field in dbox if it exists */
        gadget_nr_update_display_width (gadget, newwidth);

        /* force redraw of numberrange gadget */
        ER ( wimp_invalidate (window->window, &gadget->hdr.bbox) );
    }
    else
    {
        RectRec invalid;

        /* align the offset */
        wimp_align_point (offset);

        /* nothing to do if there is no movement */
        if (offset->x == 0 && offset->y == 0)
            return NULL;

        /* determine original bbox */
        windowedit_get_selection_bbox (window, &invalid);

        /* invalidate - allowing for ears */
        windowedit_add_ears_to_bbox (&invalid, &invalid);
        ER ( wimp_invalidate (window->window, &invalid) );

        /* resize the selected gadgets */
        {
            GadgetPtr gadget = window->gadgets;

            while (gadget != NULL)
            {
                if (gadget->selected)
                {
                    calculate_new_bbox (window, gadget, earnum, offset,
                                                    &gadget->hdr.bbox, TRUE);
                    wimp_regularise_rect (&gadget->hdr.bbox);
                }
                gadget = gadget->next;
            }
        }

        /* determine new bbox and invalidate */
        windowedit_get_selection_bbox (window, &invalid);
        windowedit_add_ears_to_bbox (&invalid, &invalid);
        ER ( wimp_invalidate (window->window, &invalid) );
    }

    /* note modification */
    ER ( protocol_send_resed_object_modified (window) );

    return NULL;
}


/*
 * Interactor for a (possibly) constrained move operation
 */

static error * resize_interactor (
    unsigned int event,
    int *buf,
    void *closure,
    Bool *consumed
)
{
    ResizeClosurePtr resize = (ResizeClosurePtr) closure;
    static Bool donepointer = FALSE;
    Bool removeptr;

    if (buf == NULL)            /* we are being asked to cancel */
    {
        /* reset pointer to normal if necessary */
        if (donepointer)
        {
            dragdrop_normal_pointer ();
            donepointer = FALSE;
        }

        /* remove resize boxes from window */
        update_resize_boxes (resize);

        /* reset auto-scroll functions */
        (void) dragdrop_scroll (NULL, NULL, NULL);

        /* cancel wimp dragbox operation */
        ER ( swi(Wimp_DragBox,  R1, 0,  END) );

        /* unset mouse coordinate bounding box just in case */
        {
            char pb[9];

            pb[0] = 1;
            pb[1] = 0x00;
            pb[2] = 0x00;
            pb[3] = 0x00;
            pb[4] = 0x00;
            pb[5] = screenx;
            pb[6] = screenx >> 8;
            pb[7] = screeny;
            pb[8] = screeny >> 8;
            return swi (OS_Word, R0, 21, R1, pb, END);
        }
    }
    
    switch (event)
    {
    case EV_NULL_REASON_CODE:
        {
            PointerInfoRec pointer;
            PointRec work;
            PointRec oldscroll, newscroll;
            Bool abouttoscroll;

            (void) swi (Wimp_GetPointerInfo,  R1, &pointer,  END);
            
            /*
             * Look to see if scrolling is about to happen.
             * If so, then we wish only to remove the resize boxes: they will
             *  be redrawn by the REDRAW_WINDOW_REQUEST code.
             * The auto-scrolling routines actually update the scroll offset,
             *  so we must preserve the original and reinstate afterwards.
             */

            oldscroll = resize->window->window->scrolloffset;
            abouttoscroll = dragdrop_scroll (resize->window->window,
                                             &pointer.position, &removeptr);
            if (abouttoscroll)
            {
                /* take note of new scroll offset before restoring */
                newscroll = resize->window->window->scrolloffset;
                resize->window->window->scrolloffset = oldscroll;
            }
            
            wimp_convert_point (ScreenToWork, resize->window->window,
                                                 &pointer.position, &work);

            work.x -= resize->original.x;
            work.y -= resize->original.y;

            if (work.x != resize->offset.x ||
                work.y != resize->offset.y || abouttoscroll)
            {
                update_resize_boxes (resize);
                resize->offset = work;

                /* don't plot new resize boxes if about to scroll */
                if (!abouttoscroll)
                    update_resize_boxes (resize);
            }
            else
            {
                wimp_start_rotate_box ();
                update_resize_boxes (resize);
                wimp_end_rotate_box ();
            }

            if (abouttoscroll)
            {
                /* restore new scroll offset ready for redraw code */
                resize->window->window->scrolloffset = newscroll;

                if (donepointer == FALSE)
                {
                    dragdrop_scroll_pointer ();
                    donepointer = TRUE;
                }
            }
            if (donepointer && removeptr)
            {
                dragdrop_normal_pointer ();
                donepointer = FALSE;
            }
        }
        break;

    case EV_KEY_PRESSED:
        {
            KeyPressPtr key = (KeyPressPtr) buf;

            dragdrop_nudge (key->code, 4);

            /* resize has priority over keyboard shortcuts */
            *consumed = (key->code != 0x1b);
        }
        break;

    case EV_REDRAW_WINDOW_REQUEST:
        {
            WindowRedrawPtr redraw = (WindowRedrawPtr) buf;
            
            if (redraw->handle == resize->window->window->handle)
            {
                PointerInfoRec pointer;
                PointRec work;

                *consumed = TRUE;

                /* redraw exposed part of window */
                windowedit_redraw_window (redraw, resize->window);

                /* determine revised position of resize boxes and redraw */
                (void) swi (Wimp_GetPointerInfo,  R1, &pointer,  END);
                wimp_convert_point (ScreenToWork, resize->window->window,
                                                  &pointer.position, &work);
                work.x -= resize->original.x;
                work.y -= resize->original.y;
                resize->offset = work;
                update_resize_boxes (resize);

                /* note modification */
                protocol_send_resed_object_modified (resize->window);
            }
        }
        break;

    case EV_USER_DRAG_BOX:
        {
            interactor_cancel();
            *consumed = TRUE;
            return resize_selected_gadgets (resize);
        }
        break;
    }
    return NULL;
}


/*
 * Called to initiate a resize of the current selection of gadgets, or (with
 *  earnum = 4) to initiate a resize of the display field inside a
 *  numberrange gadget.
 */

error * resize_start (
    WindowObjPtr window,
    GadgetPtr gadget,      /* identifies numberrange gadget if earnum == 4 */
    int earnum,
    PointPtr mousepos
)
{
    DragBoxRec drag;
    static ResizeClosureRec closure;
    PointRec work;

    interactor_cancel();
    wimp_convert_point (ScreenToWork, window->window, mousepos, &work);

    closure.window = window;
    closure.earnum = earnum;
    closure.original = work;
    closure.offset.x = 0;
    closure.offset.y = 0;
    closure.gadget = gadget;

    update_resize_boxes (&closure);

    drag.windowhandle = window->window->handle;
    drag.type = 7;
    drag.constrain = window->window->visarea;

    ER ( swi (Wimp_DragBox,  R1, &drag,  END) );

    interactor_install (resize_interactor, (void *) &closure);
    interactor_enable_events (BIT(EV_NULL_REASON_CODE));
    interactor_set_timeout (2);

    return NULL;
}
